﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ShredTheLog.Api;
using System.ComponentModel.Composition;
using System.Windows.Documents;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Windows.Controls;
using ShredTheLog.ViewModels;
using ShredTheLog.Controls;
using System.Diagnostics;
using System.Windows.Threading;
using ShredTheLog.Api.DataContracts.TextChat;

namespace ShredTheLog.Plugins
{
    static class MessageFormatterHelper
    {
        public static void ProcessSpan(Span span, Regex regex, Func<string, Inline> replaceFunc)
        {
            Contract.Requires(span != null);
            Contract.Requires(regex != null);
            Contract.Requires(replaceFunc != null);

            var inlines = span.Inlines;
            Contract.Assume(inlines != null);
            inlines.OfType<Run>().ToArray().ForEach(
                r =>
                {
                    var matches = regex.Matches(r.Text).Cast<Match>().OrderBy(match => match.Index);
                    if (matches.Any())
                    {
                        int pos = 0;
                        matches.ForEach(match =>
                        {
                            if (pos < match.Index)
                                span.Inlines.InsertBefore(r, new Run(r.Text.Substring(pos, match.Index - pos)));
                            span.Inlines.InsertBefore(r, replaceFunc(match.Value));
                            pos = match.Index + match.Length;
                        });
                        if (pos < r.Text.Length)
                            span.Inlines.InsertBefore(r, new Run(r.Text.Substring(pos)));

                        span.Inlines.Remove(r);
                    }
                });
        }

        public sealed class Task
        {
            public Regex Regex { get; set; }
            public Func<string, Inline> ReplaceFunc { get; set; }
        }
    }

    [Export(typeof(ITextFormatter))]
    [Export(typeof(IMessageFormatter))]
    [OrderMetadata(Order = 100)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class SmileyFormatter : ITextFormatter, IMessageFormatter
    {
        [Import]
        private IShredServices ShredServices 
        {
            get { return _shredServices; }
            set 
            { 
                _shredServices = value;
                ShredServices.SettingsUpdated += (sender, e) => _tasks = null;
            }
        }
        private IShredServices _shredServices;

        [ImportMany]
        private ISmileyDefinition[] SmileyDefinitions { get; set; }

        [Import]
        private IUiSettingsSection UiSettings { get; set; }

        private MessageFormatterHelper.Task[] Tasks
        {
            get
            {
                if (_tasks == null)
                {
                    Contract.Assume(SmileyDefinitions != null);
                    _tasks =
                        (from themeSmiley in UiSettings.GetTheme().Smileys
                         join definition in SmileyDefinitions on themeSmiley.SmileyDefinitionId equals definition.ID
                         from pattern in definition.Patterns
                         orderby pattern.Length descending
                         let escapedPattern = pattern.Replace(@"\", @"\\").Replace(")", @"\)").Replace("(", @"\(").Replace("*", @"\*").Replace("|", @"\|").Replace("[", @"\[").Replace("]", @"\]").Replace("{", @"\{").Replace("}", @"\}").Replace(".", @"\.")
                         select new MessageFormatterHelper.Task { Regex = new Regex(escapedPattern, RegexOptions.Compiled), ReplaceFunc = themeSmiley.SmileyFactory })
                         .ToArray();
                }
                return _tasks;
            }
        }
        private MessageFormatterHelper.Task[] _tasks;

        public void Process(string text, Span textSpan, Span embracingSpan)
        {
            Tasks.ForEach(i => MessageFormatterHelper.ProcessSpan(textSpan, i.Regex, i.ReplaceFunc));
        }

        public void Process(IMessage message, Span userNameSpan, Span textSpan, Span embracingSpan)
        {
            Process(message.Text, textSpan, embracingSpan);
        }
    }

    [Export(typeof(ITextFormatter))]
    [Export(typeof(IMessageFormatter))]
    [OrderMetadata(Order = 0)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class HyperlinkFormatter : ITextFormatter, IMessageFormatter
    {
        private const string UrlTemplate = @"\w(\w|\.|-|\?|\&|\=|/|%|#|\+|:|'|\[|\])+";

        private static readonly MessageFormatterHelper.Task[] Tasks = new MessageFormatterHelper.Task[]
        {
            new MessageFormatterHelper.Task()
            {
                Regex = new Regex(@"(http|ftp)://" + UrlTemplate, RegexOptions.Compiled), 
                ReplaceFunc = value => new ShellHyperlink(value)
            },
            new MessageFormatterHelper.Task()
            {
                Regex = new Regex(@"www\." + UrlTemplate, RegexOptions.Compiled), 
                ReplaceFunc = value => new ShellHyperlink(value, @"http://" + value)
            }
        };

        public void Process(string text, Span textSpan, Span embracingSpan)
        {
            Tasks.ForEach(t => MessageFormatterHelper.ProcessSpan(textSpan, t.Regex, t.ReplaceFunc));
        }

        public void Process(IMessage message, Span userNameSpan, Span textSpan, Span embracingSpan)
        {
            Process(message.Text, textSpan, embracingSpan);
        }
    }

    [Export(typeof(ITextFormatter))]
    [Export(typeof(IMessageFormatter))]
    [OrderMetadata(Order = 0)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class EmailFormatter : ITextFormatter, IMessageFormatter
    {
        private static readonly MessageFormatterHelper.Task[] Tasks = new MessageFormatterHelper.Task[]
        {
            new MessageFormatterHelper.Task()
            {
                Regex = new Regex(@"mailto:\w(\w|\.|-)*@\w(\w|\.|-)*\.\w(\w|\.|-)*", RegexOptions.Compiled), 
                ReplaceFunc = value => new ShellHyperlink(value)
            },
            new MessageFormatterHelper.Task()
            {
                Regex = new Regex(@"\w(\w|\.|-)*@\w(\w|\.|-)*\.\w(\w|\.|-)*", RegexOptions.Compiled), 
                ReplaceFunc = value => new ShellHyperlink(value, @"mailto:" + value)
            }
        };

        public void Process(string text, Span textSpan, Span embracingSpan)
        {
            Tasks.ForEach(t => MessageFormatterHelper.ProcessSpan(textSpan, t.Regex, t.ReplaceFunc));
        }

        public void Process(IMessage message, Span userNameSpan, Span textSpan, Span embracingSpan)
        {
            Process(message.Text, textSpan, embracingSpan);
        }
    }

    [Export(typeof(ITextFormatter))]
    [Export(typeof(IMessageFormatter))]
    [OrderMetadata(Order = 0)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class InterlinearTranslationFormatter : ITextFormatter, IMessageFormatter
    {
        [Import]
        private ITranslatorSettingsSection TranslatorSettings { get; set; }

        private IShredServices ShredServices { get; set; }

        public void Process(string text, Span textSpan, Span embracingSpan)
        {
            if (TranslatorSettings.IsActive)
            {
                Action<TranslateResponse> onCompleted =
                    response =>
                    {
                        embracingSpan.Inlines.Add(new LineBreak());
                        embracingSpan.Inlines.Add(new TranslationRun(response.Text));
                    };
                var request = new TranslateRequest(text, null, TranslatorSettings.GetTargetLanguage(), onCompleted);
                _whenTranslationRequested.OnNext(request);
            }
        }

        public void Process(IMessage message, Span userNameSpan, Span textSpan, Span embracingSpan)
        {
            Process(message.Text, textSpan, embracingSpan);
        }

        [ImportingConstructor]
        public InterlinearTranslationFormatter(IShredServices shredServices)
        {
            Contract.Requires(shredServices != null);

            ShredServices = shredServices;

            Action<Exception> onError =
                ex =>
                {
                    Trace.WriteLine(String.Format("{0}: {1}", this.GetType().Name, ex.ToString()));
                };

            // TODO: Из новой версии RX убрали Observable.Reverse, "переворачивание" порядка запросов на перевод сломалось...
            // Надо будет потом что-то придумать на замену...
            /*
            // При открытии окна чата генерируется много (около сотни) запросов на перевод уже имеющихся в окне сообщений.
            // Переводить эти сообщения целесообразно начиная с последних, так как именно они видны на экране при открытии окна.
            // То, что пользователь захочет в первую очередь перемотать лог в начало (к самым ранним сообщениям) считаем маловероятным.
            // Делаем следующее:
            var whenTranslationRequested_Reversed = _whenTranslationRequested
                // буферируем запросы на перевод и отлавливаем таким образом пачку запросов, которые были сгенерированы почти одновременно при открытии окна
                .BufferWithTime(TimeSpan.FromMilliseconds(1))
                // изменяем порядок, в котором будут переводиться сообщения отловленной пачки, на обратный, 
                // то есть сперва переведем самые последние сообщения и только под конец переведем самые ранние.
                // Если в данном случае мы имели дело с одиночным запросом на перевод (не в момент открытия окна чата), 
                // то ничего плохого от реверсирования порядка элементов в массиве из одного элемента не случится.
                // Изменив порядок следования запросов на перевод разворачиваем пачки обратно в плоскую последовательность.
                .SelectMany(i => i.Reverse());

            var whenTranslationCompleted =
                from request in whenTranslationRequested_Reversed
                from response in TranslatorSettings.GetFilteringTranslator().Translate(request, true)
                select response;
             */
            var whenTranslationCompleted =
                from request in _whenTranslationRequested
                from response in TranslatorSettings.GetFilteringTranslator().Translate(request, true)
                select response;



            whenTranslationCompleted.ObserveOn(ShredServices.Dispatcher).Subscribe(
                response =>
                {
                    try
                    {
                        ((Action<TranslateResponse>)response.Context)(response);
                    }
                    catch (Exception ex)
                    {
                        onError(ex);
                    }
                }, 
                onError);
        }

        private Subject<TranslateRequest> _whenTranslationRequested = new Subject<TranslateRequest>();
    }

    [Export(typeof(IMessageFormatter))]
    [OrderMetadata(Order = 0)]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class MessageTimeFormatter : IMessageFormatter
    {
        [Import]
        private IUiSettingsSection UiSettings { get; set; }

        public void Process(IMessage message, Span userNameSpan, Span textSpan, Span embracingSpan)
        {
            if (!UiSettings.ShowMessageTime) return;

            var messageEx = message as IMessageEx;
            if (messageEx != null)
            {
                var time = new TimeSpan(messageEx.Time.Hour, messageEx.Time.Minute, messageEx.Time.Second);
                embracingSpan.Inlines.InsertBefore(userNameSpan, new MessageTimeRun(time.ToString() + " "));
            }
        }
    }
}
